#include "Game.h"
#include "ModeGame.h"

#include "../../allegro_backend.h"

using namespace GTL;
using namespace GTL::Engine;

TGObject* TGObject::AllObjects = 0;

Point TileToPos(Point p)
{
	return p * Point(TILE_WIDTH, TILE_HEIGHT) + Point(16.0f, 16.0f);
}

Point PosToTile(Point p)
{
	return p * Point(1.0f/TILE_WIDTH, 1.0f/TILE_HEIGHT);
}

int ModeGame::Draw( float dt )
{
	Backend->ClearScreen(ColorRGBA(0,0,0,1));

	level.Draw(this);
	//player.Draw(this);

	for (TGObject* obj = TGObject::AllObjects; obj; obj = obj->Next)
	{
		obj->DoDraw(this);
	}

	DrawRect(Rect::CreateFromPosAndSize(float(player->FrontX)*TILE_WIDTH, float(player->FrontY)*TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT), ColorRGBA::Blue());

	return 0;
}

int ModeGame::Load()
{
	gfx = Backend->LoadImage("Data/gfx.png", true);

	level.Load("Data/level.txt");

	return 0;
}

int ModeGame::Start()
{
	player = CreateObject<Player>();
	player->Position = Point(500, 500);

	return 0;
}

int ModeGame::Event( GameEvent event ) 
{
	return 0;
}

int ModeGame::Stop()
{
	while (TGObject::AllObjects)
	{
		TGObject* first = TGObject::AllObjects;
		DetachObject(first);
		delete first;
	}

	return 0;
}

int ModeGame::Unload()
{
	return 0;
}

int ModeGame::Update( float dt )
{
	bool moving = false;
	int dirh = 0, dirv = 0;
	if (Backend->KeyboardState[0].Keys[KEY_A])
	{
		moving = true;
		dirh = -1;
	}
	else if (Backend->KeyboardState[0].Keys[KEY_D])
	{
		moving = true;
		dirh = 1;
	}

	if (Backend->KeyboardState[0].Keys[KEY_W])
	{
		moving = true;
		dirv = -1;
	}
	else if (Backend->KeyboardState[0].Keys[KEY_S])
	{
		moving = true;
		dirv = 1;
	}

	if (moving)
	{
		int dir = 0;
		if (dirv == 1)
		{
			if (dirh == 1)
				dir = 1;
			else if (dirh == -1)
				dir = 3;
			else dir = 2;
		}
		else if (dirv == -1)
		{
			if (dirh == 1)
				dir = 7;
			else if (dirh == -1)
				dir = 5;
			else dir = 6;
		} 
		else
		{
			if (dirh == 1)
				dir = 0;
			else if (dirh == -1)
				dir = 4;
		}

		player->MoveInDirection(dt, dir);
	} else
		player->Stand();

	if (Backend->MouseState[0].Buttons & 2)
	{
		Point plfront = Point(player->FrontX, player->FrontY);
		if (level.IsValid(plfront))
		{
			Level::Tile& t = level.GetTile(plfront);
			if (!t.Building && t.Ground == GROUND_LANE)
			{
				SlowTrap* trap = CreateObject<SlowTrap>();
				level.SetBuilding(plfront, trap, B_TRAP);
			} else if (t.Building && t.Building->State != TGBuilding::STATE_DESTROYED)
			{
				t.Building->BuildOrRepair(player->GetStat(STAT_ENGINEERING));

				player->Build();
			}
		}
	} 

	player->Selected = player->GetRect().Contains(Backend->MouseState[0].GetPos());

	if ((rand()%500) == 30)
	{
		size_t i = 0;
		for (; i<level.Tiles.size(); i++)
			if (level.Tiles[i].BuildingType == B_SPAWNER) break;

		if (i < level.Tiles.size())
		{
			printf("Created spawner!\n");
			Spawner* spawner = (Spawner*)level.Tiles[i].Building;

			Mob* mobek = CreateObject<Mob>();
			mobek->Type = MOB_WALKER;
			mobek->Path = &spawner->Path;
			mobek->NextTargetPoint = 1;
			mobek->SetAnim(MOB_WALKER, 4, 300);
			mobek->Position = TileToPos(spawner->Path[0].Pos);
		}
	}

	Viewport = Rect::CreateCenteredAtPoint(player->Position, 1280, 720);

	for (TGObject* obj = TGObject::AllObjects; obj;)
	{
		obj->DoUpdate(dt, this);
		if (obj->Zombie)
		{
			TGObject* zombie = obj;
			obj = obj->Next;
			DestroyObject(zombie);
		} else
			obj = obj->Next;
	}

	return 0;
}

GTL::Engine::Rect ModeGame::GetRectForFrame( int frame )
{
	int wt = int(gfx->Width)/TILE_WIDTH;
	float x = float(frame%wt);
	float y = float(frame/wt);
	return Rect(x*TILE_WIDTH, y*TILE_HEIGHT, x*TILE_WIDTH+TILE_WIDTH, y*TILE_HEIGHT+TILE_HEIGHT);
}

void ModeGame::DrawRect( const GTL::Engine::Rect& rect, const GTL::Engine::ColorRGBA& color, const GTL::Engine::ColorRGBA& fillcolor )
{
	Backend->DrawRect(rect-Viewport.p1, color, fillcolor);
}

void ModeGame::DrawFrameRotated( int frame, GTL::Engine::Point position, float rotation, GTL::Engine::Point scale )
{
	Rect rect = GetRectForFrame(frame);
	gfx->Draw(rect, rect.GetCenter()-rect.p1, position-Viewport.p1, rotation, scale);
}

void ModeGame::DrawFrameSimple( int frame, GTL::Engine::Point position )
{
	Rect rect = GetRectForFrame(frame);
	gfx->Draw(rect, rect.GetMovedTo(position-Viewport.p1));
}

void ModeGame::DrawLine( float x1, float y1, float x2, float y2, GTL::Engine::ColorRGBA color )
{
	Backend->DrawLine(x1-Viewport.p1.X, y1-Viewport.p1.Y, x2-Viewport.p1.X, y2-Viewport.p1.Y, color);
}

/// TGObject

int TGObject::GetImage()
{
	int curTim = Life%AnimationSpeed;
	float progress = float(curTim)/float(AnimationSpeed);
	int animFrame = int(float(AnimationFrames)*progress);
	return Animation + animFrame;
}

float AngleForDir(int dir)
{
	return float(dir)*(GTL::Math::Constants<float>::FullCircleInRadians()/8.0f);
}

void TGObject::Draw(ModeGame* mode)
{
	int frame = GetImage();
	//Rect rect = mode->GetRectForFrame(frame);

	mode->DrawFrameRotated(frame, Position, Rotation, Point(Scale, Scale));
	if (Selected)
		mode->DrawRect(GetRect(), ColorRGBA::Red());
		//Backend->DrawRect(GetRect(), ColorRGBA::Red());
	if (Health != MaxHealth || Selected)
	{
		float healthpercent = float(Health)/float(MaxHealth);
		//Backend->DrawRect(GetRect().GetResized(Point(healthpercent, 3.0f/32.0f)) + Point(0, -5), ColorRGBA::Red(), ColorRGBA::Red());
		mode->DrawRect(GetRect().GetResized(Point(healthpercent, 3.0f/32.0f)) + Point(0, -5), ColorRGBA::Red(), ColorRGBA::Red());
	}
}

TGObject::TGObject() : 
	Position(0, 0), Selected(false), Health(1000), MaxHealth(1000), Radius(16.0f), Scale(1.0f), Rotation(0), Life(0), 
	Animation(0), AnimationFrames(4), AnimationSpeed(1000), Next(0), Prev(0), Zombie(false)
{

}

void TGObject::SetAnim( int start, int frames, int animtime )
{
	Animation = start;
	AnimationFrames = frames;
	AnimationSpeed = animtime;
}

GTL::Engine::Rect TGObject::GetRect()
{
	return Rect::CreateCenteredAtPoint(Position,TILE_WIDTH, TILE_HEIGHT);
}

void TGObject::Update( float dt, ModeGame* game )
{
}

void TGObject::DoUpdate( float dt, ModeGame* game )
{
	int frames = int(dt*1000.0f);
	Life += frames;

	this->Update(dt, game);
}

void TGObject::DoDraw( ModeGame* game )
{
	this->Draw(game);
}

/// Player

Player::Player() : State(S_STANDING)
{
	Stats[STAT_SPEED] = 150.0f;
}

void Player::Update( float dt, ModeGame* game )
{
	Creature::Update(dt, game);

	Point pos = Backend->MouseState[0].GetPos() + game->Viewport.p1;
	FrontX = int(pos.X/TILE_WIDTH);
	FrontY = int(pos.Y/TILE_HEIGHT);

	Rotation = (pos - Position).GetAngle();

	//Point forward = GTL::Geometry2D::GenericAngle2D<float>(Rotation);
	//Point pos = Position + forward*32.0f;
	//FrontX = int(pos.X/TILE_WIDTH);
	//FrontY = int(pos.Y/TILE_HEIGHT);
}

void Player::MoveInDirection( float dt, int dir )
{
	State = S_WALKING;

	Point p = GTL::Geometry2D::GenericAngle2D<float>(AngleForDir(dir));
	Position += p * dt * GetStat(STAT_SPEED);

	SetAnim(0, 4, 500);
}

void Player::Build()
{
	State = S_BUILDING;

	SetAnim(16, 4, 300);
}

void Player::Stand()
{
	State = S_STANDING;

	SetAnim(1,1,1000);
}

void Player::SteppedOn( TGBuilding* building )
{
	Creature::SteppedOn(building);
}

void Player::DoDamage( int amount )
{
	Creature::DoDamage(amount);
}

/// Level

void Level::SetSize( int w, int h )
{
	Destroy();
	Tiles.resize(w*h, Tile());
	Width = w;
	Height = h;	
}

void Level::Draw( ModeGame* mode )
{
	for (int y=0; y<Height; y++)
	{
		for (int x=0; x<Width; x++)
		{
			Rect trect = Rect(x*TILE_WIDTH, y*TILE_HEIGHT, x*TILE_WIDTH+TILE_WIDTH, y*TILE_HEIGHT+TILE_HEIGHT);
			Tile& t = GetTile(x,y);

			//game->gfx->Draw(game->GetRectForFrame(t.Ground), trect);
			mode->DrawFrameSimple(t.Ground, Point(x*TILE_WIDTH, y*TILE_HEIGHT));
			//if (t.BuildingType != B_NONE)
				//t.Building->Draw(game);
				//game->gfx->Draw(game->GetRectForFrame(GetTile(x,y).BuildingType), trect);
		}
	}

	for (int y=0; y<Height; y++)
	{
		mode->DrawLine(0, y*TILE_HEIGHT, Width*TILE_WIDTH, y*TILE_HEIGHT, ColorRGBA::White(0.2f));
	}

	for (int x=0; x<Width; x++)
	{
		mode->DrawLine(x*TILE_WIDTH, 0, x*TILE_WIDTH, Height*TILE_HEIGHT, ColorRGBA::White(0.2f));
	}
}

Level::Tile& Level::GetTile( int x, int y )
{
	return Tiles[y*Width+x];
}

Level::Tile& Level::GetTile( Point p )
{
	return GetTile(int(p.X), int(p.Y));
}

bool Level::IsValid( Point p )
{
	return Rect(0, 0, Width, Height).Contains(p);
}

void Level::Load( const char* filename )
{
	Reflector ref;
	ref.Start(Reflector::REF_Deserializing, filename);

	int w, h;
	ref.R(w);
	ref.R(h);
	SetSize(w, h);

	if (Core)
		DestroyObject(Core);

	Core = CreateObject<TGBuilding>();
	Core->Type = B_CORE;
	Core->SetAnim(B_CORE, 1, 1000);
	Core->State = TGBuilding::STATE_WORKING;
	ref.R(Core->TilePos);
	SetBuilding(Core->TilePos, Core, B_CORE);

	int spawners = 0;
	ref.R(spawners);

	while (spawners--)
	{
		Spawner* sp = CreateObject<Spawner>();
		ref.R(*sp);
		SetBuilding(sp->TilePos, sp, B_SPAWNER);

		PlaceLanes(sp->Path);
	}
}

void Level::Destroy()
{
	for (size_t i=0; i<Tiles.size(); i++)
	{
		Tiles[i].Ground = GROUND_GRASS;
		DestroyObject(Tiles[i].Building);
	}
}

void Level::SetBuilding( Point pos, TGBuilding* sp, int type )
{
	Tile& t = GetTile(pos);

	DestroyObject(t.Building);

	sp->TilePos = pos;
	sp->CalcPos();

	t.Building = sp;
	t.BuildingType = type;
}

void Level::PlaceLanes( const eastl::vector<Spawner::Waypoint>& waypoints )
{
	for (size_t i=0; i<waypoints.size()-1; i++)
	{
		PlaceLane(waypoints[i].Pos, waypoints[i+1].Pos);
	}
}

struct PlaceLaneTileFunctor
{
	Level* Lev;
	PlaceLaneTileFunctor(Level* lev) : Lev(lev) {}
	void operator() (Point p) { Lev->PlaceLaneTile(p); }
};

void Level::PlaceLane( Point p1, Point p2 )
{
	GTL::Geometry2D::SelectGridTilesUnderSegment(GTL::Geometry2D::GenericSegment2D<float>(p1, p2), PlaceLaneTileFunctor(this));
}

void Level::PlaceLaneTile( Point p )
{
	GetTile(p).Ground = GROUND_LANE;
}

/// Mob

void Mob::Move( float dt )
{
	if (Path)
	{
		eastl::vector<Spawner::Waypoint>& path = *Path;
		Point nexttarget = TileToPos(path[NextTargetPoint].Pos);
		Point nextpos = Position.GetMovedTowards(nexttarget, GetStat(STAT_SPEED), dt);

		/// TODO: Calculate collision with other objects and adjust nextpos accordingly

		if (nextpos == nexttarget || (nextpos-nexttarget).GetDotProduct(Position-nexttarget) < 0)
		{ /// moved beyond target point
			if (NextTargetPoint == path.size()-1)
			{	/// arrived at destination
				
			} else
			{
				Position = nexttarget;
				NextTargetPoint++;
			}
		} else
		{
			Position = nextpos;
		}

		Point towards = TileToPos(path[NextTargetPoint].Pos) - Position;
		if (towards.GetLengthSquared() > 0.001f)
		{
			Rotation = GTL::Geometry2D::Just::GetAngle(towards.X, towards.Y);
		}
	}
}

void Mob::Update( float dt, ModeGame* game )
{
	Creature::Update(dt, game);

	if (Zombie)
		return;

	if (game->player->Position.IsCircleCollision(Position, GetSize(), game->player->GetSize()))
	{
		game->player->CreatureDamage(this, DAMAGE_MELEE);
	}

	Move(dt);
}

void Mob::SteppedOn( TGBuilding* build )
{
	Creature::SteppedOn(build);

	if (build->Type == B_CORE)
		Zombie = true;
}

/// Creature

void Creature::CreatureDamage( Creature* mob, int type )
{
	if (type == DAMAGE_MELEE)
		DoDamage(mob->GetStat(STAT_DAMAGE_MELEE));
	else if (type == DAMAGE_PROJECTILE)
		DoDamage(mob->GetStat(STAT_DAMAGE_PROJECTILE));
}

void Creature::DoDamage( int amount )
{
	Health -= amount;
}

float Creature::GetStat( int stat )
{
	return Stats[stat] * StatMultipliers[stat];
}

void Creature::Update( float dt, ModeGame* game )
{
	TGBuilding* prev_building = OnBuilding;
	if (game->level.IsValid(PosToTile(Position)))
	{
		OnBuilding = game->level.GetTile(PosToTile(Position)).Building;
	}

	if (prev_building != OnBuilding)
	{
		RecalculateStatuses();

		/// TODO:
		//if (prev_building) SteppedOff(prev_building);
		if (OnBuilding) SteppedOn(OnBuilding);
	}
}

void Creature::SteppedOn( TGBuilding* build )
{
	if (build->State != TGBuilding::STATE_WORKING)
		return;
}

void Creature::RecalculateStatuses()
{
	for (int i=0; i<STAT_ALL; i++)
	{
		StatMultipliers[i] = 1.0f;
	}

	/// TODO: Buffs, items, etc.

	if (OnBuilding && OnBuilding->State == TGBuilding::STATE_WORKING)
	{
		if (OnBuilding->Type == B_TRAP)
		{
			Trap* trap = (Trap*)OnBuilding;
			trap->ModifyCreature(this);
		}
	}
}

/// SlowTrap

void SlowTrap::ModifyCreature( Creature* cr )
{
	cr->StatMultipliers[STAT_SPEED] = 0.5f;
}

void TGBuilding::BuildOrRepair( int amount )
{
	if (State == STATE_DESTROYED) return;

	Health += amount;
	if (Health > MaxHealth)
	{
		Health = MaxHealth;
		if (State == STATE_BUILDING)
			State = STATE_WORKING;
	}
}